import json
import os
import threading
import time
import uuid
from dataclasses import dataclass, asdict
from datetime import datetime
from typing import List, Optional, Dict, Any, Callable
import streamlit as st

from .book import Book
from .config_manager import ConfigManager


@dataclass
class DownloadTask:
    """Represents a download task in the queue"""
    task_id: str
    book_id: str
    book_title: str
    download_type: str  # "full", "volume", "separate_volumes"
    volume_name: Optional[str] = None
    status: str = "pending"  # "pending", "processing", "completed", "failed"
    progress: float = 0.0
    created_at: str = None
    started_at: Optional[str] = None
    completed_at: Optional[str] = None
    error_message: Optional[str] = None
    file_paths: List[str] = None
    
    def __post_init__(self):
        if self.created_at is None:
            self.created_at = datetime.now().isoformat()
        if self.file_paths is None:
            self.file_paths = []
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert task to dictionary for JSON serialization"""
        return asdict(self)
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'DownloadTask':
        """Create task from dictionary"""
        return cls(**data)


class DownloadQueue:
    """Manages download queue with background processing"""
    
    def __init__(self, downloader_callback: Optional[Callable] = None):
        self.config = ConfigManager()
        self._queue: List[DownloadTask] = []
        self._current_task: Optional[DownloadTask] = None
        self._is_running = False
        self._should_stop = False
        self._lock = threading.Lock()
        self._worker_thread: Optional[threading.Thread] = None
        self._results: List[DownloadTask] = []  # Completed tasks
        self._queue_file = os.path.join(os.getcwd(), 'downloads', 'queue_state.json')
        self._downloader_callback = downloader_callback
        
        # Load persisted queue
        self._load_queue_state()
        
        # Start worker thread if we have a downloader callback
        if self._downloader_callback:
            self._start_worker()
    
    def add_task(self, book_id: str, book_title: str, download_type: str, volume_name: Optional[str] = None) -> str:
        """Add a new download task to the queue"""
        task = DownloadTask(
            task_id=str(uuid.uuid4()),
            book_id=book_id,
            book_title=book_title,
            download_type=download_type,
            volume_name=volume_name
        )
        
        with self._lock:
            # Check if task already exists
            existing_task = self._find_task(book_id, download_type, volume_name)
            if existing_task and existing_task.status in ["pending", "processing"]:
                return existing_task.task_id
            
            self._queue.append(task)
            self._save_queue_state()
        
        return task.task_id
    
    def remove_task(self, task_id: str) -> bool:
        """Remove a pending task from the queue"""
        with self._lock:
            for i, task in enumerate(self._queue):
                if task.task_id == task_id and task.status == "pending":
                    self._queue.pop(i)
                    self._save_queue_state()
                    return True
        return False
    
    def get_queue_status(self) -> Dict[str, Any]:
        """Get current queue status"""
        with self._lock:
            pending_tasks = [t for t in self._queue if t.status == "pending"]
            processing_tasks = [t for t in self._queue if t.status == "processing"]
            completed_tasks = [t for t in self._results if t.status == "completed"]
            failed_tasks = [t for t in self._results if t.status == "failed"]
            
            total_tasks = len(self._queue) + len(self._results)
            completed_count = len(completed_tasks)
            
            return {
                "is_running": self._is_running,
                "current_task": self._current_task.to_dict() if self._current_task else None,
                "pending_tasks": [task.to_dict() for task in pending_tasks],
                "processing_tasks": [task.to_dict() for task in processing_tasks],
                "completed_tasks": [task.to_dict() for task in completed_tasks],
                "failed_tasks": [task.to_dict() for task in failed_tasks],
                "total_tasks": total_tasks,
                "completed_count": completed_count,
                "overall_progress": completed_count / total_tasks if total_tasks > 0 else 0.0
            }
    
    def pause_queue(self):
        """Pause queue processing"""
        self._is_running = False
        self._save_queue_state()
    
    def resume_queue(self):
        """Resume queue processing"""
        self._is_running = True
        if not self._worker_thread or not self._worker_thread.is_alive():
            self._start_worker()
        self._save_queue_state()
    
    def clear_completed(self):
        """Clear completed and failed tasks from results"""
        with self._lock:
            self._results = []
            self._save_queue_state()
    
    def retry_failed_task(self, task_id: str) -> bool:
        """Retry a failed task"""
        with self._lock:
            for i, task in enumerate(self._results):
                if task.task_id == task_id and task.status == "failed":
                    # Reset task status and move back to queue
                    task.status = "pending"
                    task.progress = 0.0
                    task.error_message = None
                    task.started_at = None
                    task.completed_at = None
                    
                    # Move from results back to queue
                    self._results.pop(i)
                    self._queue.append(task)
                    self._save_queue_state()
                    return True
        return False
    
    def stop(self):
        """Stop the queue processor"""
        self._should_stop = True
        self._is_running = False
        if self._worker_thread and self._worker_thread.is_alive():
            self._worker_thread.join(timeout=5)
    
    def _find_task(self, book_id: str, download_type: str, volume_name: Optional[str]) -> Optional[DownloadTask]:
        """Find existing task with same parameters"""
        for task in self._queue + self._results:
            if (task.book_id == book_id and 
                task.download_type == download_type and 
                task.volume_name == volume_name):
                return task
        return None
    
    def _start_worker(self):
        """Start the background worker thread"""
        if self._worker_thread and self._worker_thread.is_alive():
            return
        
        self._should_stop = False
        self._is_running = True
        self._worker_thread = threading.Thread(target=self._worker_loop, daemon=True)
        self._worker_thread.start()
    
    def _worker_loop(self):
        """Main worker loop for processing queue"""
        if not self._downloader_callback:
            return
            
        retry_attempts = self.config.get('batch_download', 'retry_attempts') or 3
        retry_delay = self.config.get('batch_download', 'retry_delay') or 5
        
        while not self._should_stop:
            if not self._is_running:
                time.sleep(1)
                continue
            
            # Get next task
            task = self._get_next_task()
            if not task:
                time.sleep(1)
                continue
            
            # Process task
            self._current_task = task
            self._update_task_status(task.task_id, "processing", started_at=datetime.now().isoformat())
            
            success = False
            for attempt in range(retry_attempts):
                try:
                    # Create progress callback
                    def progress_callback(progress: float, message: str = ""):
                        self._update_task_progress(task.task_id, progress)
                    
                    # Process the download using the callback
                    file_paths = self._process_download_with_callback(task, progress_callback)
                    
                    # Mark as completed
                    self._update_task_status(
                        task.task_id, 
                        "completed", 
                        completed_at=datetime.now().isoformat(),
                        file_paths=file_paths
                    )
                    success = True
                    break
                    
                except Exception as e:
                    error_msg = str(e)
                    if attempt < retry_attempts - 1:
                        # Wait before retry
                        time.sleep(retry_delay)
                        continue
                    else:
                        # Final failure
                        self._update_task_status(
                            task.task_id,
                            "failed",
                            completed_at=datetime.now().isoformat(),
                            error_message=error_msg
                        )
            
            # Move completed/failed task to results
            with self._lock:
                self._queue = [t for t in self._queue if t.task_id != task.task_id]
                self._results.append(task)
                self._current_task = None
                self._save_queue_state()
    
    def _get_next_task(self) -> Optional[DownloadTask]:
        """Get the next pending task from queue"""
        with self._lock:
            for task in self._queue:
                if task.status == "pending":
                    return task
        return None
    
    def _update_task_status(self, task_id: str, status: str, **kwargs):
        """Update task status and additional fields"""
        with self._lock:
            # Find task in queue or results
            all_tasks = self._queue + self._results
            for task in all_tasks:
                if task.task_id == task_id:
                    task.status = status
                    for key, value in kwargs.items():
                        if hasattr(task, key):
                            setattr(task, key, value)
                    break
        self._save_queue_state()
    
    def _update_task_progress(self, task_id: str, progress: float):
        """Update task progress"""
        with self._lock:
            all_tasks = self._queue + self._results
            for task in all_tasks:
                if task.task_id == task_id:
                    task.progress = progress
                    break
    
    def _process_download_with_callback(self, task: DownloadTask, progress_callback) -> List[str]:
        """Process a download task using the downloader callback and return file paths"""
        if not self._downloader_callback:
            raise RuntimeError("No downloader callback available")
        
        # Call the downloader callback with task and progress callback
        return self._downloader_callback(task, progress_callback)
    
    def _save_queue_state(self):
        """Save queue state to file"""
        try:
            # Ensure downloads directory exists
            os.makedirs(os.path.dirname(self._queue_file), exist_ok=True)
            
            state = {
                "queue": [task.to_dict() for task in self._queue],
                "results": [task.to_dict() for task in self._results],
                "is_running": self._is_running,
                "last_save": datetime.now().isoformat()
            }
            
            with open(self._queue_file, 'w', encoding='utf-8') as f:
                json.dump(state, f, indent=2, ensure_ascii=False)
        except Exception as e:
            print(f"Failed to save queue state: {e}")
    
    def _load_queue_state(self):
        """Load queue state from file"""
        try:
            if os.path.exists(self._queue_file):
                with open(self._queue_file, 'r', encoding='utf-8') as f:
                    state = json.load(f)
                
                # Restore queue and results
                self._queue = [DownloadTask.from_dict(task_data) for task_data in state.get("queue", [])]
                self._results = [DownloadTask.from_dict(task_data) for task_data in state.get("results", [])]
                
                # Reset any processing tasks to pending
                for task in self._queue:
                    if task.status == "processing":
                        task.status = "pending"
                        task.progress = 0.0
                        task.started_at = None
                
                # Restore running state if configured to auto-start
                auto_start = self.config.get('batch_download', 'auto_start_queue')
                if auto_start is None:
                    auto_start = True
                self._is_running = auto_start
                
        except Exception as e:
            print(f"Failed to load queue state: {e}")
            self._queue = []
            self._results = []